package com.wifibrowser.lib;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;
import java.util.TreeSet;

/**
 * @author Administrator
 * @Email guoyil199@163.com
 * @version V1.0
 * @Date 2015年3月10日 上午10:10:35
 * @Title <pre>
 * 异常捕获
 * </pre>
 * @Description <pre>
 * Application:
 * @Override
 * public void onCreate() {
 * super.onCreate();
 * //注册App异常崩溃处理器
 *  AppUncaughExceptionHandler.getInstance().register(getApplicationContext());
 * }
 * </pre>
 */
public class AppUncaughExceptionHandler implements UncaughtExceptionHandler {
	public static final String TAG = "AppUncaughExceptionHandler";
	// public static final boolean DEBUG = Log.isDebug();
	private static AppUncaughExceptionHandler INSTANCE;
	private Context mContext;
	private Thread.UncaughtExceptionHandler mDefaultHandler;
	private Properties mDeviceCrashInfo = new Properties();
	private static final String VERSION_NAME = "versionName";
	private static final String VERSION_CODE = "versionCode";
	private static final String STACK_TRACE = "STACK_TRACE";
	private static final String CRASH_REPORTER_EXTENSION = ".log";
	// Service service;
	private Class<? extends Service> serviceClazz;

	private AppUncaughExceptionHandler() {
	}

	public static AppUncaughExceptionHandler getInstance() {
		if (INSTANCE == null)
			INSTANCE = new AppUncaughExceptionHandler();
		return INSTANCE;
	}

	/**
	 * 初始化,注册Context对象, 获取系统默认的UncaughtException处理器, 设置该CrashHandler为程序的默认处理器
	 * 
	 * @param ctx
	 * @param clazz
	 *            服务
	 */
	public void register(Context ctx, Class<? extends Service> clazz) {
		mContext = ctx;
		this.serviceClazz = clazz;
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	/**
	 * 初始化,注册Context对象, 获取系统默认的UncaughtException处理器, 设置该CrashHandler为程序的默认处理器
	 * 
	 * @param ctx
	 */
	public void register(Context ctx) {
		mContext = ctx;
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		if (!handleException(ex) && mDefaultHandler != null) {
			mDefaultHandler.uncaughtException(thread, ex);
		} else {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				Log.e(TAG, "Error : ", e);
			}
		}
		if (serviceClazz != null) {
			if (isRunning(mContext, serviceClazz.getName())) {
				mContext.stopService(new Intent(mContext, serviceClazz));
			}
		}
		android.os.Process.killProcess(android.os.Process.myPid());
		System.exit(1);
	}

	/**
	 * 判断服务是否启动
	 * 
	 * @param context
	 * @param serviceName
	 * @return
	 */
	public boolean isRunning(Context context, String serviceName) {
		ActivityManager myAM = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);
		ArrayList<RunningServiceInfo> runningServices = (ArrayList<RunningServiceInfo>) myAM
				.getRunningServices(40);
		for (int i = 0; i < runningServices.size(); i++)// 循环枚举对比
		{
			String className = runningServices.get(i).service.getClassName();
			if (className.equals(serviceName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 开发者可以根据自己的情况来自定义异常处理逻辑
	 * @param ex
	 * @return true:如果处理了该异常信息;否则返回false
	 */
	private boolean handleException(final Throwable ex) {
		if (ex == null) {
			return true;
		}
		/*final String msg = ex.getMessage();
		new Thread() {// 使用Toast来显示异常信息
			@Override
			public void run() {//Toast 显示需要出现在一个线程的消息队列中
				Looper.prepare();
				Toast.makeText(mContext, "抱歉程序出错了:" +msg,Toast.LENGTH_LONG).show(); 
				if (mContext != null) {
					Log.e(mContext.getPackageName(), msg, ex);
				} else {
					Log.e(msg, ex);
				}
				Looper.loop();
			}
		}.start();*/
		// 收集设备信息
		collectCrashDeviceInfo(mContext);
		//if (!Log.isDebug()) {
		// 保存错误报告文件
		String crashFileName = saveCrashInfoToFile(ex);
		Log.d(TAG, "ErrorFile:" + crashFileName);
		Log.d(TAG, "ErrorMessage:" + ex.getMessage(), ex);
		// 发送错误报告到服务器
		sendCrashReportsToServer(mContext);
		//Log.e(TAG, "ErrorMessage:"+ex.getMessage(), ex);
		//}else{
		//	Log.e(TAG, "ErrorMessage:"+ex.getMessage(), ex);
		//}
		return true;
	}

	/**
	 * 收集程序崩溃的设备信息
	 * 
	 * @param ctx
	 */
	public void collectCrashDeviceInfo(Context ctx) {
		try {
			// Class for retrieving various kinds of information related to the
			// application packages that are currently installed on the device.
			// You can find this class through getPackageManager().
			PackageManager pm = ctx.getPackageManager();
			// getPackageInfo(String packageName, int flags)
			// Retrieve overall information about an application package that is
			// installed on the system.
			// public static final int GET_ACTIVITIES
			// Since: API Level 1 PackageInfo flag: return information about
			// activities in the package in activities.
			PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(),
					PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				// public String versionName The version name of this package,
				// as specified by the <manifest> tag's versionName attribute.
				mDeviceCrashInfo.put(VERSION_NAME,
						pi.versionName == null ? "not set" : pi.versionName);
				// public int versionCode The version number of this package,
				// as specified by the <manifest> tag's versionCode attribute.
				mDeviceCrashInfo.put(VERSION_CODE, pi.versionCode + "");
			}
		} catch (NameNotFoundException e) {
			Log.e(TAG, "Error while collect package info", e);
		}
		// 使用反射来收集设备信息.在Build类中包含各种设备信息,
		// 例如: 系统版本号,设备生产商 等帮助调试程序的有用信息
		// 返回 Field 对象的一个数组，这些对象反映此 Class 对象所表示的类或接口所声明的所有字段
		Field[] fields = Build.class.getDeclaredFields();
		for (Field field : fields) {
			try {
				// setAccessible(boolean flag)
				// 将此对象的 accessible 标志设置为指示的布尔值。
				// 通过设置Accessible属性为true,才能对私有变量进行访问，不然会得到一个IllegalAccessException的异常
				field.setAccessible(true);
				String value = field.get(null) + "";
				mDeviceCrashInfo.put(field.getName(), value);
				// Log.d(TAG, field.getName() + " : " + value);
			} catch (Exception e) {
				Log.e(TAG, "Error while collect crash info", e);
			}
		}
	}

	/**
	 * 保存错误信息到文件中
	 * 
	 * @param ex
	 * @return
	 */
	private String saveCrashInfoToFile(Throwable ex) {
		Writer info = new StringWriter();
		PrintWriter printWriter = new PrintWriter(info);
		// printStackTrace(PrintWriter s)
		// 将此 throwable 及其追踪输出到指定的 PrintWriter
		ex.printStackTrace(printWriter);
		ex.printStackTrace();
		// getCause() 返回此 throwable 的 cause；如果 cause 不存在或未知，则返回 null。
		Throwable cause = ex.getCause();
		while (cause != null) {
			cause.printStackTrace(printWriter);
			cause = cause.getCause();
		}
		Log.e(TAG, "saveCrashInfoToFile", ex);
		// toString() 以字符串的形式返回该缓冲区的当前值。
		String result = info.toString();
		printWriter.close();
		mDeviceCrashInfo.put(STACK_TRACE, result);
		try {
			long timestamp = System.currentTimeMillis();
			String fileName = "crash_" + timestamp + CRASH_REPORTER_EXTENSION;
			// 保存文件
			FileOutputStream trace = null;
			if (!Environment.getExternalStorageState().equals(
					Environment.MEDIA_MOUNTED)) {// sd卡存储可写
				trace = mContext.openFileOutput(fileName, Context.MODE_PRIVATE);
			} else {
				// String root =
				// Environment.getExternalStorageDirectory().getPath();
				String path = getSDAndroidDir();
				Log.e(TAG, path);
				/*
				 * if (mContext != null) { path = root + "/Android/data/" +
				 * mContext.getPackageName()+ "/log/"; } else { path = root +
				 * "/Android/data/log/"; }
				 */
				File file = new File(path);
				if (!file.exists()) {
					file.mkdirs();
				}
				fileName = path + fileName;
				file = new File(fileName);
				trace = new FileOutputStream(file);
			}

			mDeviceCrashInfo.store(trace, "");
			trace.flush();
			trace.close();
			return fileName;
		} catch (Exception e) {
			Log.e(TAG, "an error occured while writing report file...", e);
		}
		return null;
	}

	public String getSDAndroidDir() {
		if (Environment.getExternalStorageState().equals(
				Environment.MEDIA_MOUNTED)) {
			return mContext.getExternalCacheDir().getAbsolutePath();
		} else {
			return mContext.getCacheDir().getAbsolutePath();
		}

	}

	private void sendCrashReportsToServer(Context ctx) {
		String[] crFiles = getCrashReportFiles(ctx);
		if (crFiles != null && crFiles.length > 0) {
			TreeSet<String> sortedFiles = new TreeSet<String>();
			sortedFiles.addAll(Arrays.asList(crFiles));
			for (String fileName : sortedFiles) {
				File cr = new File(ctx.getFilesDir(), fileName);
				if (postReport(cr)) {
					cr.delete();//删除已发送的报告
				}

			}
		}
	}

	private String[] getCrashReportFiles(Context ctx) {
		File filesDir = ctx.getFilesDir();
		// 实现FilenameFilter接口的类实例可用于过滤器文件名
		FilenameFilter filter = new FilenameFilter() {
			// accept(File dir, String name)
			// 测试指定文件是否应该包含在某一文件列表中。
			public boolean accept(File dir, String name) {
				return name.endsWith(CRASH_REPORTER_EXTENSION);
			}
		};
		// list(FilenameFilter filter)
		// 返回一个字符串数组，这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录
		return filesDir.list(filter);
	}

	private boolean postReport(File file) {
		return false;
	}

	public void sendPreviousReportsToServer() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				sendCrashReportsToServer(mContext);
			}
		}).start();
	}
}